///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CBUFFERSTREAM_H_
#define _BUOLA_IO_CBUFFERSTREAM_H_

#include <buola/buola.h>
#include <buola/io/cresult.h>
#include <buola/io/curi.h>
#include <buola/io/cstream.h>
#include <buola/container/csimplebuffer.h>

namespace buola { namespace io {

class CBufferStream : public UShared
{
protected:
    CBufferStream()=default;

public:
    virtual ~CBufferStream()=default;
    
public:
    CResult Read(uint8_t *pData,size_t pLen)
    {
        const uint8_t *lNewPtr=mRPtr+pLen;
        if(lNewPtr<=mREnd)
        {
            memcpy(pData,mRPtr,pLen);
            mRPtr=lNewPtr;
            return CResult(pLen);
        }
        else
        {
            return DoRead(pData,pLen);
        }
    }
    
    CResult Write(const uint8_t *pData,size_t pLen)
    {
        uint8_t *lNewPtr=mWPtr+pLen;
        if(lNewPtr<=mWEnd)
        {
            memcpy(mWPtr,pData,pLen);
            mWPtr=lNewPtr;
            return CResult(pLen);
        }
        else
        {
            return DoWrite(pData,pLen);
        }
    }

    //this zero copy interface is designed for use with libarchive, but I'm sure it has other uses. The block is valid
    //until another function is called on the stream.
    CResult ReadNextBlock(const uint8_t *&pBlock)
    {
        if(mRPtr>=mREnd)
        {
            CResult lResult=DoUnderflow();
            if(mRPtr>=mREnd)
            {
                pBlock=nullptr;
                return lResult;
            }
        }
        
        CResult lResult(mREnd-mRPtr);
        pBlock=mRPtr;
        mRPtr=mREnd;
        return lResult;
    }
    
    template<typename tPredicate>
    CResult ReadUntil(uint8_t *pData,size_t pLen,tPredicate pPredicate,EReadFlags pFlags)
    {
        ssize_t lRead=0;
        while(pLen)
        {
            size_t lMax=std::min(mREnd-mRPtr,(ptrdiff_t)pLen);
            for(int i=0;i<lMax;i++)
            {
                if(pPredicate(mRPtr[i]))
                {
                    if(pFlags&EReadFlags::INCLUDELAST)
                    {
                        pData[i]=mRPtr[i];
                        mRPtr+=i+1;
                        return lRead+i+1;
                    }
                    else if(pFlags&EReadFlags::EXTRACTLAST)
                    {
                        mRPtr+=i+1;
                        return lRead+i;
                    }
                    else
                    {
                        mRPtr+=i;
                        return lRead+i;
                    }
                }
                pData[i]=mRPtr[i];
            }
            pLen-=lMax;
            mRPtr+=lMax;
            pData+=lMax;
            lRead+=lMax;
            
            if(pLen)
            {
                ssize_t lRes=DoUnderflow();
                if(lRes<=0)
                    return lRead?lRead:lRes;
            }
        }
        return CResult(lRead,EResult::NOTFOUND);
    }

    template<typename tContainer,typename tPredicate>
    CResult ReadUntil(tContainer &pContainer,tPredicate pPredicate,EReadFlags pFlags)
    {
        pContainer.clear();
        while(true)
        {
            const uint8_t *lBegin=mRPtr;
            while(mRPtr<mREnd)
            {
                if(pPredicate(*mRPtr))
                {
                    if(pFlags&EReadFlags::INCLUDELAST)
                    {
                        ++mRPtr;
                        pContainer.append((const char*)lBegin,mRPtr-lBegin);
                    }
                    else
                    {
                        pContainer.append((const char*)lBegin,mRPtr-lBegin);
                        if(pFlags&EReadFlags::EXTRACTLAST)
                            ++mRPtr;
                    }
                    return CResult(pContainer.size());
                }
                ++mRPtr;
            }
            pContainer.append((const char*)lBegin,mRPtr-lBegin);
            
            CResult lRes=DoUnderflow();
            if(!lRes.Count())
                return CResult(pContainer.size(),lRes.Flags()|EResult::NOTFOUND);
        }
    }

    CResult Ignore(size_t pLen)
    {
        const uint8_t *lNewPtr=mRPtr+pLen;
        if(lNewPtr<=mREnd)
        {
            mRPtr=lNewPtr;
            return CResult(pLen);
        }
        else
        {
            return DoIgnore(pLen);
        }
    }
    
    CResult Ignore(size_t pLen,uint8_t pCheck)
    {
        ssize_t lIgnored=0;
        while(pLen)
        {
            size_t lMax=std::min(mREnd-mRPtr,(ptrdiff_t)pLen);
            for(int i=0;i<lMax;i++)
            {
                if(mRPtr[i]!=pCheck)
                    throw XData("mismatch in Ignore");
            }
            pLen-=lMax;
            mRPtr+=lMax;
            lIgnored+=lMax;
            
            if(pLen)
            {
                CResult lRes=DoUnderflow();
                if(!lRes.Count())
                    return CResult(lIgnored,lRes.Flags());
            }
        }
        return CResult(lIgnored);
    }

    template<typename tPredicate>
    CResult IgnoreUntil(tPredicate pPredicate,bool pInclude)
    {
        size_t lIgnored=0;
        while(true)
        {
            const uint8_t *lBegin=mRPtr;
            while(mRPtr<mREnd)
            {
                if(pPredicate(*mRPtr))
                {
                    if(pInclude) ++mRPtr;
                    lIgnored+=mRPtr-lBegin;
                    return CResult(lIgnored);
                }
                ++mRPtr;
            }
            lIgnored+=mRPtr-lBegin;
            
            CResult lRes=DoUnderflow();
            if(!lRes.Count())
                return CResult(lIgnored,lRes.Flags()|EResult::NOTFOUND);
        }
    }

    off_t Seek(off_t pPos,ESeek pMode=ESeek::ABSOLUTE)
    {
        return DoSeek(pPos,pMode);
    }
    
    off_t Tell()
    {
        return DoTell();
    }
    
    bool Peek(uint8_t &pByte)
    {
        if(mRPtr==mREnd)
        {
            DoUnderflow();
            if(mRPtr==mREnd)
                return false;
        }
        pByte=*mRPtr;
        return true;
    }
    
    void Bump()
    {
        ++mRPtr;
    }
    
    void Flush()
    {
        DoFlush();
    }
    
private:
    ///Called when a read cannot be performed with the data available in the buffer.
    ///
    ///When overriding, you can safely assume that the function will never be called when there is enough data in the buffer.
    virtual CResult DoRead(uint8_t *pData,size_t pLen)=0;
    virtual CResult DoIgnore(size_t pLen)=0;
    virtual CResult DoUnderflow()=0;

    virtual CResult DoWrite(const uint8_t *pData,size_t pLen)=0;
    virtual void DoFlush()=0;

    virtual off_t DoSeek(off_t pPos,ESeek pMode)=0;
    virtual off_t DoTell()=0;
    
protected:
    uint8_t *mWPtr;
    uint8_t *mWEnd;
    const uint8_t *mRPtr;
    const uint8_t *mREnd;
};

io::PBufferStream open_buffered(const io::CURI &pURI,EMode pMode);

bool get_line(const PBufferStream &pStream,std::string &pString,const char pDelim='\n');
bool get_line(const PBufferStream &pStream,std::wstring &pString,const char pDelim='\n');

inline CSimpleBuffer<uint8_t> read_simple_buffer(const PBufferStream &pStream,size_t pSize)
{
    CSimpleBuffer<uint8_t> lBuffer(pSize);
    if(pStream->Read(lBuffer.begin(),pSize).Count()!=pSize)
        throw XData("can't read simple buffer");
    return lBuffer;
}

inline std::string read_string(const PBufferStream &pStream,size_t pSize)
{
    std::string lString(pSize,' ');
    if(pStream->Read((uint8_t*)&lString[0],pSize).Count()!=pSize)
        throw XData("can't read string");
    return lString;
}

/*namespace io*/ } /*namespace buola*/ }

#endif
